﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="YieldPatternExtensions.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// <summary>
//   Defines the YieldPatternExtensions type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.Threading
{
    using System;
    using System.Collections.Generic;
    using System.Threading;

    public static class YieldPatternExtensions
    {
        private class YieldPatternAdapter : IAsyncResult
        {
            #region Fields

            private readonly YieldPatternContext _context;
            private IDispatcher _dispatcher;
            private readonly Func<IEnumerable<ITask>> _getEnumerableMethod;
            private AsyncCallback _callback;
            private long _outstanding;
            private bool _isCompleted;

            #endregion

            private YieldPatternAdapter(Func<IEnumerable<ITask>> getEnumerableMethod,
               YieldPatternContext context)
            {
                // always use current sync context to sync NextTask()
                _dispatcher = Dispatcher.FromCurrentSynchronizationContext();

                _getEnumerableMethod = getEnumerableMethod;
                _context = context;
            }

            private IEnumerator<ITask> Enumerator { get; set; }

            private IAsyncResult Begin(AsyncCallback callback, object userData)
            {
                // save callback so we can notify completion
                _callback = callback;

                // get enumerator
                Enumerator = _getEnumerableMethod().GetEnumerator();
                if (Enumerator == null)
                {
                    Complete();
                }

                // get next task
                Process();

                // return 
                return this;
            }

            private void End(IAsyncResult result)
            {
                if (_context.Error != null)
                {
                    throw _context.Error;
                }
            }

            private void Complete()
            {
                _isCompleted = true;
                _callback(this);
            }

            private void Process()
            {
                while (Enumerator.MoveNext())
                {
                    var task = Enumerator.Current;
                    if (task != null)
                    {
                        task.Start(TaskCompleted);
                        Interlocked.Increment(ref _outstanding);
                        if (!_context.IsParallel)
                        {
                            return;
                        }
                    }
                }

                if (_outstanding == 0)
                {
                    Complete();
                }
            }

            private void TaskCompleted(ITaskResult result)
            {
                // Must Evaluate the decrement first
                if (Interlocked.Decrement(ref _outstanding) == 0 || _context.IsParallel)
                {
                    _dispatcher.BeginInvoke(Process);
                }
            }

            public static ITask<T> CreateTask<T>(Func<IEnumerable<ITask>> getEnumerableMethod,
               YieldPatternContext<T> context, TaskFactory taskFactory)
            {
                if (context == null)
                {
                    context = new YieldPatternContext<T>();
                }

                var adapter = new YieldPatternAdapter(getEnumerableMethod, context);

                return taskFactory.FromAsync(adapter.Begin, s =>
                      {
                          adapter.End(s);
                          return context.Result;
                      });
            }

            public static ITask CreateTask(Func<IEnumerable<ITask>> getEnumerableMethod,
               YieldPatternContext context, TaskFactory taskFactory)
            {
                var adapter = new YieldPatternAdapter(getEnumerableMethod,
                   context ?? new YieldPatternContext());
                return taskFactory.FromAsync(adapter.Begin, adapter.End);
            }

            #region IAsyncResult Members

            bool IAsyncResult.IsCompleted
            {
                get { return _isCompleted; }
            }

            WaitHandle IAsyncResult.AsyncWaitHandle
            {
                get { return null; }
            }

            object IAsyncResult.AsyncState
            {
                get { return null; }
            }

            bool IAsyncResult.CompletedSynchronously
            {
                get { return false; }
            }

            #endregion
        }

        #region ITask

        public static ITask FromYield(this TaskFactory instance, Func<IEnumerable<ITask>> getEnumerableFunc)
        {
            return FromYield(instance, getEnumerableFunc, null);
        }

        public static ITask FromYield(this TaskFactory instance, Func<IEnumerable<ITask>> getEnumerableFunc,
                                 YieldPatternContext context)
        {
            return YieldPatternAdapter.CreateTask(getEnumerableFunc, context, instance);
        }

        public static ITask FromYield(this TaskFactory instance, Func<YieldPatternContext, IEnumerable<ITask>> getEnumerableFunc,
                                 YieldPatternContext context)
        {
            // must create context here if we want to provide access to it in the clusure
            if (context == null)
            {
                context = new YieldPatternContext();
            }

            return FromYield(instance, () => getEnumerableFunc(context), context);
        }

        public static ITask FromYield(this TaskFactory instance, Func<YieldPatternContext, IEnumerable<ITask>> getEnumerableFunc)
        {
            return FromYield(instance, getEnumerableFunc, null);
        }

        #endregion

        #region ITask<T>

        public static ITask<T> FromYield<T>(this TaskFactory instance, Func<IEnumerable<ITask>> getEnumerableFunc)
        {
            return FromYield<T>(instance, getEnumerableFunc, null);
        }

        public static ITask<T> FromYield<T>(this TaskFactory instance, Func<IEnumerable<ITask>> getEnumerableFunc,
                                       YieldPatternContext<T> context)
        {
            return YieldPatternAdapter.CreateTask(getEnumerableFunc, context, instance);
        }

        public static ITask<T> FromYield<T>(this TaskFactory instance, Func<YieldPatternContext<T>, IEnumerable<ITask>> getEnumerableFunc,
                                       YieldPatternContext<T> context)
        {
            if (context == null)
            {
                context = new YieldPatternContext<T>();
            }
            return FromYield(instance, () => getEnumerableFunc(context), context);
        }

        public static ITask<T> FromYield<T>(this TaskFactory instance, Func<YieldPatternContext<T>, IEnumerable<ITask>> getEnumerableFunc)
        {
            return FromYield(instance, getEnumerableFunc, null);
        }

        #endregion
    }
}

